using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
using server.Services;
using server.Models;
using server.Extensions;
using System.Text.Json;
namespace server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BooksController : ControllerBase
    {
        private LibraryContext db;
        private TokenService tokenService;
        public BooksController(LibraryContext db, TokenService tokenService)
        {
            this.db = db;
            this.tokenService = tokenService;
        }
        [HttpGet("count")]
        public async Task<int> GetBookCount()
        {
            return await db.Books.CountAsync();
        }

        [HttpGet("{id:int}")]
        public async Task<IActionResult> GetBook(int id)
        {
            var book = await db.Books.FindAsync(id);
            return book is null ? NotFound() : Ok(book);
        }

        [HttpGet]
        public async Task<IActionResult> GetBooks([FromQuery] int offset = 0, [FromQuery] int count = 20)
        {
            if (offset < 0 || count < 0)
            {
                return BadRequest($"Invalid offset or count:{offset},{count}");
            }
            var total_count = await db.Books.CountAsync();
            var books = await db.Books.OrderBy(b => b.ID)
                            .Skip(offset * count)
                            .Take(count)
                            .AsNoTracking()
                            .ToListAsync();
            return Ok(new BooksResponse
            {
                TotalCount = total_count,
                PageOffset = offset,
                PageSize = count,
                Books = books
            });
        }
        [HttpGet("search")]
        public async Task<IActionResult> SearchBooks(string type, string keyword, int page_offset = 0, int page_size = 20)
        {
            if (string.IsNullOrEmpty(keyword))
            {
                return BadRequest("Keyword can not be empty!");
            }
            IQueryable<Book> books;
            switch (type)
            {
                case "name":
                    books = db.Books.AsNoTracking()
                                .Where(b => b.BookName.Contains(keyword.Trim()));
                    break;
                case "author":
                    books = db.Books.AsNoTracking()
                            .Where(b => string.IsNullOrEmpty(b.Author) ? false : b.Author.Contains(keyword.Trim()));
                    break;
                case "publisher":
                    books = db.Books.AsNoTracking()
                            .Where(b => string.IsNullOrEmpty(b.Publisher) ? false : b.Publisher.Contains(keyword.Trim()));
                    break;
                default:
                    return BadRequest("Invalid search type:" + type);
            }
            var result = new BooksResponse
            {
                PageOffset = page_offset,
                PageSize = page_size,
                TotalCount = await books.CountAsync(),
                Books = await books.Skip(page_offset * page_size).Take(page_size).ToListAsync(),
            };
            return Ok(result);
        }
        [HttpGet("template")]
        public IActionResult GetBookTemplate()
        {
            // if (!CanAddBook()){
            //     return Forbid403("No right to add new book!");
            // }
            var type = typeof(NewBook);
            return Ok(Schema.GetSchema(type));
        }

        private async Task<bool> HasRepeated(NewBook new_book)
        {
            return await db.Books.AnyAsync(b => b.BookName == new_book.BookName && b.Author == new_book.Author && b.Publisher == new_book.Publisher);

        }

        [HttpPost("add")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.AddBook})]
        public async Task<IActionResult> AddBook(NewBook new_book)
        {
           
            if (await HasRepeated(new_book))
            {
                return UnprocessableEntity("相同书籍已经存在!");
            }
            var book = new Book(new_book);
            db.Add(book);
            db.SaveChanges();
            return CreatedAtAction(nameof(AddBook), book);
        }
        [HttpGet("import/options")]
        public IActionResult GetImportOptions()
        {
            return Ok(Schema.GetSchema(typeof(BookImportOptions)));
        }
        [HttpPost("import")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.BookManager})]
        public async Task<IActionResult> ImportBooks(List<NewBook> new_books)
        {
            if (ModelState.IsValid == false)
            {
                System.Console.WriteLine(ModelState);
                return BadRequest(ModelState);
            }
            var repeat_count = 0;
            // var next_id = db.Books.OrderByDescending(b => b.ID).Select(b => b.ID).FirstOrDefault() + 1;
            foreach (var new_book in new_books)
            {
                if (await HasRepeated(new_book))
                {
                    repeat_count++;
                    continue;
                }
                var book = new Book(new_book);
                // book.ID = next_id++;
                db.Add(book);
            }
            var import_count = await db.SaveChangesAsync();
            return Ok(new ImportResponse
            {
                AcceptCount = new_books.Count(),
                RepeatCount = repeat_count,
                ImportCount = import_count,
                Message = $"成功导入{import_count}本书籍, 忽略重复书籍{repeat_count}本!"
            });
        }

        [HttpPost("update/{id:int}")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.BookManager})]
        public async Task<IActionResult> UpdateBook(int id, NewBook update_book)
        {
            // System.Console.WriteLine(update_book.BookName);
            var book = await db.FindAsync<Book>(id);
            if (book is null)
            {
                return NotFound();
            }
            book.Update(update_book);
            db.Update(book);
            await db.SaveChangesAsync();
            var updated=await db.Books.FindAsync(id);
            return Ok(updated);
        }


        [HttpDelete("delete/{id:int}")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.BookManager})]
        public async Task<IActionResult> DeleteBook(int id)
        {
            var book = await db.Books.FindAsync(id);
            if (book is null)
            {
                return NotFound("Book not found:" + id);
            }
            db.Remove(book);
            await db.SaveChangesAsync();
            return Ok(book);
        }
        [HttpGet("borrow/{bid:int}")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.BorrowBook})]
        public async Task<IActionResult> BorrowBook(int bid){
            var book = await db.Books.FindAsync(bid);
            if (book is null)
            {
                return NotFound("Book not found:"+bid);
            }
            var uid_str=HttpContext.Request.Headers["X-UID"];
            var uid= int.Parse(uid_str);
            var user= await db.Users.FindAsync(uid);
            if (db.BorrowRecords.Any(br=>
                br.Book==book && br.User==user && br.ReturnDate.HasValue==false
            ))
            {
                return BadRequest("Can not borrow already borrowed book!");
            }
            var record= new BorrowRecord(user!,book);
            await db.BorrowRecords.AddAsync(record);
            await db.SaveChangesAsync();
            return Ok(record);
        }

        [HttpGet("return/{rid:int}")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.BorrowBook})]
        public async Task<IActionResult> ReturnBook(int rid){
            var record=await db.BorrowRecords.Include(br=>br.Book).FirstAsync(br=>br.Id==rid);
            if (record is null)
            {
                return NotFound("Borrow Record not found:"+rid);
            }
            if (record.IsReturned)
            {
                return BadRequest("Book has been returned!");
            }
            record.Return();
            await db.SaveChangesAsync();
            return Ok(new BorrowRecordResponse(record));
        }

        [HttpGet("record/{uid:int}")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.BorrowBook})]
        public async Task<IActionResult> GetUserBorrowRecords(int uid){
            var user= await db.Users.FindAsync(uid);
            if (user is null)
            {
                return NotFound("User not found:"+uid);
            }
            var records= await db.BorrowRecords.Include(br=>br.Book).Where(br=>br.User==user).OrderByDescending(br=>br.BorrowDate).ToArrayAsync();
            return Ok(records.Select(r=> new BorrowRecordResponse(r)));
        }

        [HttpGet("record/all")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.SuperManager})]
        public async Task<IActionResult> GetAllBorrowRecords(){
            var records= await db.BorrowRecords.Include(br=>br.Book).Include(br=>br.User).OrderByDescending(br=>br.BorrowDate).ToArrayAsync();
            return Ok(records.Select(br=>new UserBorrowRecordResponse(br)));
        }

        private User? GetUser(){
            var uid_str=HttpContext.Request.Headers["X-UID"];
            if (String.IsNullOrEmpty(uid_str))
            {
                return null;
            }
            if (!int.TryParse(uid_str,out int uid))
            {
                System.Console.WriteLine("Invalid uid:"+uid_str);
                return null;
            }
            return db.Users.Find(uid);
        }

        [HttpDelete("record/{rid:int}")]
        [TypeFilter(typeof(TokenFilterAttribute), Arguments = new Object[]{Right.SuperManager})]
        public async Task<IActionResult> DeleteBorrowRecord(int rid){
            var record=await db.BorrowRecords.FindAsync(rid);
            if (record is null)
            {
                return NotFound("Borrow Record not found:"+rid);
            }
            db.BorrowRecords.Remove(record);
            await db.SaveChangesAsync();
            return Ok(record);
        }
        ~BooksController()
        {
            db.Dispose();
        }

    }


}
